/**
 * 支持自动映射、自动验证和自动完成的表单模型类
 * Author: 陆楚良
 * Version: 1.2.1
 * Date: 2016/07/14
 * QQ: 874449204
 *
 * https://git.oschina.net/luchg/formModel.js
 *
 * License: http://opensource.org/licenses/MIT
 */
!function(){
    function factory($){
        "use strict";
        var undefined = void 0;
        var isObj = function(e){return $.type(e)=="object"};
        var isNum = function(e){return $.type(e)=="number"};
        var isFun = function(e){return $.type(e)=="function"};
        /**
         * 以字节为单位计算字符串长度
         */
        function lengthAt(str, len){
            len = len||2;
            var sum = 0;
            for (var i = 0; i < str.length; i++) {
                if ((str.charCodeAt(i) >= 0) && (str.charCodeAt(i) <= 255))
                    sum = sum + 1;
                else
                    sum = sum + len;
            }
            return sum;
        };
        var RegExps={
                "require"   : /\S+/,    // 不为空
                "email"     : /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/,      // 邮箱
                "url"       : /^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/,   // 链接地址
                "currency"  : /^(0|[1-9][0-9]*)(\.[0-9]{1,2})?$/,   // 货币
                "number"    : /^\d+$/,                              // 数字
                "zip"       : /^\d{6}$/,                            // 邮编
                "integer"   : /^(0|-?[1-9][0-9]*)$/,                // 整数
                "pinteger"  : /^(0|[1-9][0-9]*)$/,                  // 正整数
                "double"    : /^-?(0|[1-9][0-9]*)\.[0-9]+$/,        // 浮点数
                "pdouble"   : /^(0|[1-9][0-9]*)\.[0-9]+$/,          // 正浮点数
                "english"   : /^[A-Za-z]+$/,                        // 英文字母
                "chinese"   : /^[\u4e00-\u9fa5]+$/                  // 汉字
            };
        /**
         * 将jQuery.serializeArray的数组解析为更容易查询的对象
         * @param  {Array} serializeArray   来自$.serializeArray的数组
         * @return {Object}
         */
        function serializeObject(formElement){
            var serializeArray = $(formElement).serializeArray();
            var data = {};
            var len  = {};
            $.each(serializeArray, function(k,v){
                if(v.name.indexOf("[")!=0){
                    var name = v.name;
                    var parse= v.name.match(/^([^\[]+)\[(.*)\]/);
                    if(parse){
                        name = parse[1];
                        var key  = parse[2];
                        if(key===""){
                            if(!len.hasOwnProperty(name)){
                                len[name] = -1;
                            }
                            key = len[name] += 1;
                        }
                        if(!data.hasOwnProperty(name) || !isObj(data[name])){
                            data[name] = {};
                        }
                        data[name][key] = v.value;
                    }else{
                        data[name] = v.value;
                    }
                }
            });
            return data;
        };
        function Model(){
            this.$data = {};
            this.setMap(this.$m());
            this.setVali(this.$v());
            this.setAuto(this.$a());
            var initialize = this.initialize;
            initialize.apply(this, [].slice.call(arguments));
        }
        /**
         * 继承
         */
        Model.extend = function(prototype){
            var Class = function(){
                Model.apply(this, [].slice.call(arguments));
            };
            Class.extend = this.extend;
            Class.parent = this;
            Class.create = this.create;
            Class.EXISTS_VALIDATE = this.EXISTS_VALIDATE;
            Class.MUST_VALIDATE   = this.MUST_VALIDATE;
            Class.VALUE_VALIDATE  = this.VALUE_VALIDATE;
            Class.serializeObject     = this.serializeObject;
            $.extend(Class.prototype, this.prototype, prototype);
            return Class;
        };
        /**
         * 快速实例化
         */
        Model.create = function(prototype){
            return new (this.extend(prototype));
        };
        // 存在时验证
        Model.EXISTS_VALIDATE = 0;
        // 必须验证
        Model.MUST_VALIDATE   = 1;
        // 值不为空时验证
        Model.VALUE_VALIDATE  = 2;
        Model.serializeObject     = serializeObject;
        // 映射
        function Map(){
            var data = {},$m = this.$m;
            $.each(this.$data, function(k, v){
                if($m.hasOwnProperty(k)){
                    k = $m[k];
                }
                data[k] = v;
            });
            return data;
        }
        // 验证
        function Check(){
            var $v  = this.$v;
            var ischeck = {};
            var error;
            var errors  = [];
            var name,value,RESULT;
            for(var i=0;i<this.$v.length;i++){
                error = null;
                name  = $v[i].name;
                if(this.$patch && ischeck.hasOwnProperty(name)){
                    // 批量验证时跳过已经验证过的字段
                    continue;
                }
                if(this.$data.hasOwnProperty(name)){
                    value  = this.$data[name];
                    RESULT = $v[i].type.indexOf("!")!==0;
                    if($v[i].condition==this.constructor.VALUE_VALIDATE && value===""){
                        // 条件：值不为空的时候验证
                        // 结果：值为空，跳过验证
                        continue;
                    }
                    switch($v[i].type.replace(/^\!/,"").toLowerCase()){
                        // 正则表达式
                        case "regexp":
                            var v1 = $.type($v[i].action)=="string" ? RegExps[$v[i].action] : $v[i].action;
                            if(v1.test(value)!=RESULT){
                                error = {name: name, msg: $v[i].error};
                            }
                            break;
                        // 回调验证，函数，通过返回bool值以确认验证的通过性
                        case "callback":
                            if($v[i].action.call(this, value, name)!=RESULT){
                                error = {name: name, msg: $v[i].error};
                            }
                            break;
                        // 关联其它字段，例如注册表单中的确认密码可直接关联密码，对应的操作为密码的name值
                        case "confirm":
                            if((this.$data[$v[i].action]===value)!=RESULT){
                                error = {name: name, msg: $v[i].error};
                            }
                            break;
                        // 等于某值
                        case "equal":
                            if(($v[i].action===value)!=RESULT){
                                error = {name: name, msg: $v[i].error};
                            }
                            break;
                        // 在指定数组范围内
                        case "in":
                            if(($.inArray(value, $v[i].action)>-1)!=RESULT){
                                error = {name: name, msg: $v[i].error};
                            }
                            break;
                        // 字符长度
                        case "length":
                            var v1 = value.length;
                            var v2 = isNum($v[i].action) ? [$v[i].action, $v[i].action] : $v[i].action;
                            if(((v2[0]===null || v1>=v2[0]) && (v2[1]===null || v1<=v2[1]))!=RESULT){
                                error = {name: name, msg: $v[i].error};
                            }
                            break;
                        // 字节长度，可使用extra额外补充设置一个汉字占用的字节数，缺省字节数为2
                        case "lengthat":
                            var v1 = lengthAt(value, $v[i].extra||2);
                            var v2 = isNum($v[i].action) ? [$v[i].action, $v[i].action] : $v[i].action;
                            if(((v2[0]===null || v1>=v2[0]) && (v2[1]===null || v1<=v2[1]))!=RESULT){
                                error = {name: name, msg: $v[i].error};
                            }
                            break;
                        // 验证数字的范围
                        case "between":
                            var v1 = Number(value);
                            if((v1>=$v[i].action[0] && v1<=$v[i].action[1])!=RESULT){
                                error = {name: name, msg: $v[i].error};
                            }
                    }
                }else if($v[i].condition===this.constructor.MUST_VALIDATE){
                    error = {name: name, msg: $v[i].error};
                }
                if(error){
                    error.msg = $.type(error.msg)=="function" ? error.msg.call(this, name, value) : error.msg;
                    if(this.$patch){
                        ischeck[error.name] = true;
                        errors.push(error);
                    }else{
                        // 非批量验证时候，只要其中一个字段不通过验证，则中止
                        return error;
                    }
                }
            }
            if(errors.length>0){
                return errors;
            }
        }
        // 自动完成
        function Auto(){
            var self = this,$data = this.$data;
            $.each(this.$a, function(k, v){
                // {name: "", action:"", type: ""}
                switch (v.type.toLowerCase()){
                    // 用其它字段填充，表示填充的内容是一个其他字段的值
                    case "field":
                        $data[v.name] = $data[v.action];
                        break;
                    // 为空则忽略
                    case "ignore":
                        if($data[v.name]===""){
                            delete $data[v.name];
                        }
                        break
                    case "default":     // 自动判断字符串或回调
                    case "string":      // 可以直接以字符串填值
                    case "callback":    // 使用回调方法返回
                    default:
                        if(isFun(v.action)){
                            $data[v.name] = v.action.call(self, $data.hasOwnProperty(v.name) ? $data[v.name] : undefined, v.name);
                        }else{
                            $data[v.name] = v.action;
                        }
                        break;
                }
            });
            return $data;
        }
        Model.prototype = {
            initialize: function(){},
            $patch: false,
            $m : function(){ return {} },
            $v : function(){ return [] },
            $a : function(){ return [] },
            check : function(data, callback){
                this.$data = data;
                this.$data = Map.call(this);
                var ret = Check.call(this);
                if(ret){
                    callback.call(this, ret);
                }else{
                    this.$data = Auto.call(this)
                    callback.call(this, null, this.$data);
                }
            },
            setMap: function($m){
                this.$m = $m;
            },
            setVali: function($v){
                var self = this;
                this.$v = [];
                $.each($v, function(k, v){
                    var v1 = {};
                    if(!isObj(v)){
                        // [*字段名, *验证操作, *错误提示, 验证方法, 验证条件, 额外补充]
                        if(v[0]!==undefined)v1.name     = v[0];
                        if(v[1]!==undefined)v1.action   = v[1];
                        if(v[2]!==undefined)v1.error    = v[2];
                        if(v[3]!==undefined)v1.type     = v[3];
                        if(v[4]!==undefined)v1.condition= v[4];
                        if(v[5]!==undefined)v1.extra    = v[5];
                    }else{
                        // {name:字段名, action:验证操作, error:错误提示, type:验证方法, condition:验证条件, extra:额外补充}
                        v1 = v;
                    }
                    self.$v.push($.extend({
                        // 必填：字段名
                        name  : "",
                        // 必填：操作
                        action: undefined,
                        // 必填：错误信息
                        error : "",
                        // 选填：验证方法
                        type  : "regexp",
                        // 选填：验证条件
                        condition: Model.EXISTS_VALIDATE,
                        // 选填：额外设置（例：lengthAt中用于设置一个汉字占用的字节数）
                        extra : undefined
                    }, v1));
                });
            },
            setAuto: function($a){
                var self = this;
                this.$a = [];
                if(isObj($a)){
                    // 简洁法
                    // {
                    //     字段1: 操作1,
                    //     字段2: 操作2
                    //     ......
                    // }
                    $.each($a, function(k, v){
                        self.$a.push({
                            // 必填：字段名
                            name  : k,
                            // 必填：完成操作
                            action: v,
                            // 选填：操作方法
                            type  : "default"
                        });
                    });
                }else{
                    // 完整方法
                    // [
                    //     {...},
                    //     {...},
                    //     [...]
                    // ]
                    $.each($a, function(k, v){
                        if(!isObj(v)){
                            var v1 = {};
                            if(!isObj(v)){
                                // [*字段名, *完成操作, 操作方法]
                                if(v[0]!==undefined)v1.name     = v[0];
                                if(v[1]!==undefined)v1.action   = v[1];
                                if(v[2]!==undefined)v1.type     = v[2];
                            }else{
                                // {name:字段名, action:完成操作, type:操作方法}
                                v1 = v;
                            }
                            self.$a.push($.extend({
                                // 必填：字段名
                                name  : "",
                                // 必填：完成操作
                                action: undefined,
                                // 选填：操作方法
                                type  : "default"
                            }, v1));
                        }
                    });
                }
            }
        }
        return Model;
    }
    // RequireJS && SeaJS
    if (typeof define === "function") {
        define(function() {
            return factory;
        });
    // NodeJS
    } else if (typeof exports !== "undefined") {
        module.exports = factory;
    } else {
        window.formModel = factory($);
    }
}();
